/*

 * analyzer.c
 *
 *  Created on: 07/10/2013
 *      Author: vitor
 */
#include "assembler.h"

// Variáveis globais... por favor não me mate, Carla.
char sourceFile[65536];
char dataSegment[4096];
char textSegment[4096];
int executionStartAddress;
short dataSegmentSize;
int textSegmentSize;
int currentChar;
int currentAddress;
instrInfo currentInstruction;

// intValue nessa lista é o endereço da label
stringIntList* labelList;

// intValue nessa lista é o opcode
stringIntList* ITypeList;

// intValue nessa é o funct
stringIntList* RTypeList;

// intValue nessa é o valor do reg
stringIntList* registerList;

void populateRegisterList() {
	stringInt toAdd;

	init(&registerList);

	strcpy(toAdd.string, "r0");
	toAdd.intValue = 0;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r1");
	toAdd.intValue = 1;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r2");
	toAdd.intValue = 2;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r3");
	toAdd.intValue = 3;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r4");
	toAdd.intValue = 4;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r5");
	toAdd.intValue = 5;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r6");
	toAdd.intValue = 6;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r7");
	toAdd.intValue = 7;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r8");
	toAdd.intValue = 8;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r9");
	toAdd.intValue = 9;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r10");
	toAdd.intValue = 10;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r11");
	toAdd.intValue = 11;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r12");
	toAdd.intValue = 12;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r13");
	toAdd.intValue = 13;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r14");
	toAdd.intValue = 14;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r15");
	toAdd.intValue = 15;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r16");
	toAdd.intValue = 16;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r17");
	toAdd.intValue = 17;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r18");
	toAdd.intValue = 18;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r19");
	toAdd.intValue = 19;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r20");
	toAdd.intValue = 20;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r21");
	toAdd.intValue = 21;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r22");
	toAdd.intValue = 22;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r23");
	toAdd.intValue = 23;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r24");
	toAdd.intValue = 24;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r25");
	toAdd.intValue = 25;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r26");
	toAdd.intValue = 26;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r27");
	toAdd.intValue = 27;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "r28");
	toAdd.intValue = 28;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "rv");
	toAdd.intValue = 29;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "bp");
	toAdd.intValue = 30;
	addStringInt(&registerList, toAdd);

	strcpy(toAdd.string, "sp");
	toAdd.intValue = 31;
	addStringInt(&registerList, toAdd);

}

void populateRTypeList() {
	stringInt toAdd;

	init(&RTypeList);

	strcpy(toAdd.string, "and");
	toAdd.intValue = 0;
	addStringInt(&RTypeList, toAdd);

	strcpy(toAdd.string, "or");
	toAdd.intValue = 1;
	addStringInt(&RTypeList, toAdd);

	strcpy(toAdd.string, "nor");
	toAdd.intValue = 2;
	addStringInt(&RTypeList, toAdd);

	strcpy(toAdd.string, "add");
	toAdd.intValue = 3;
	addStringInt(&RTypeList, toAdd);

	strcpy(toAdd.string, "sub");
	toAdd.intValue = 4;
	addStringInt(&RTypeList, toAdd);

	strcpy(toAdd.string, "mult");
	toAdd.intValue = 5;
	addStringInt(&RTypeList, toAdd);

	strcpy(toAdd.string, "div");
	toAdd.intValue = 6;
	addStringInt(&RTypeList, toAdd);

	strcpy(toAdd.string, "rem");
	toAdd.intValue = 7;
	addStringInt(&RTypeList, toAdd);

}

void populateITypeList() {
	stringInt toAdd;

	init(&ITypeList);

	strcpy(toAdd.string, "nop");
	toAdd.intValue = 0;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "stop");
	toAdd.intValue = 1;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "cmp");
	toAdd.intValue = 2;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "jeq");
	toAdd.intValue = 3;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "jne");
	toAdd.intValue = 4;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "jlt");
	toAdd.intValue = 5;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "jgt");
	toAdd.intValue = 6;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "jle");
	toAdd.intValue = 7;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "jge");
	toAdd.intValue = 8;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "jmp");
	toAdd.intValue = 9;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "call");
	toAdd.intValue = 10;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "ret");
	toAdd.intValue = 11;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "lw");
	toAdd.intValue = 12;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "sw");
	toAdd.intValue = 13;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "addi");
	toAdd.intValue = 14;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "syscall");
	toAdd.intValue = 15;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "push");
	toAdd.intValue = 16;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "pop");
	toAdd.intValue = 17;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "lui");
	toAdd.intValue = 18;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "ori");
	toAdd.intValue = 19;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "sll");
	toAdd.intValue = 20;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "srl");
	toAdd.intValue = 21;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "sra");
	toAdd.intValue = 22;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "pushi");
	toAdd.intValue = 23;
	addStringInt(&ITypeList, toAdd);

	strcpy(toAdd.string, "addiu");
	toAdd.intValue = 24;
	addStringInt(&ITypeList, toAdd);

}

void eatComma() {
	while (sourceFile[currentChar] != ',') {
		currentChar++;
	}
	currentChar++;
}

int isWhiteSpace() {
	return sourceFile[currentChar] == ' ' || sourceFile[currentChar] == '\t'
			|| sourceFile[currentChar] == '\n';
}

int endOfProgram() {
	return sourceFile[currentChar] == -1;
}

int endOfSegment() {
	return endOfProgram() || sourceFile[currentChar] == '.';
}

void eatWhiteSpaces() {
	while (isWhiteSpace() && !endOfProgram()) {
		currentChar++;
	}
}

void readWord(char* result) {
	int i = 0;

	eatWhiteSpaces();

	while (!isWhiteSpace() && !endOfProgram()) {
		result[i] = sourceFile[currentChar];
		i++;
		currentChar++;
	}
	result[i] = '\0';
}

void handleArglessIInstruction(int opcode) {
	currentInstruction.opcode = opcode;
	currentInstruction.rs = 0;
	currentInstruction.rt = 0;
	currentInstruction.imm = 0;
}

void readImmediateValue(char* result) {
	readWord(result);
}

int isNumber(char* string) {
	int i = 0;

	if (string[0] == '-') {
		i++;
	}
	while (string[i] != '\0') {
		if (string[i] < '0' || string[i] > '9') {
			return 0;
		}
		i++;
	}

	return 1;
}

short readImmediate() {
	char immediateValue[10];
	stringInt label;

	eatWhiteSpaces();
	readImmediateValue(immediateValue);
	if (isNumber(immediateValue)) {
		return (short) atoi(immediateValue);
	} else {
		searchByString(labelList, immediateValue, &label);
		return (short) label.intValue;
	}
}

void handleOneImmediateArgIInstruction(int opcode) {
	currentInstruction.opcode = opcode;
	currentInstruction.rs = 0;
	currentInstruction.rt = 0;
	currentInstruction.imm = readImmediate();
}

void readRegisterName(char* result) {
	int i = 0;

	while (sourceFile[currentChar] != ',' && !isWhiteSpace()) {
		result[i] = sourceFile[currentChar];
		currentChar++;
		i++;
	}
	result[i] = '\0';
}

int readRegister() {
	stringInt _register;
	char registerName[10];

	eatWhiteSpaces();
	readRegisterName(registerName);
	if (!searchByString(registerList, registerName, &_register)) {
		fprintf(stderr, "Nao conheco esse registrador: %s\n", registerName);
		exit(1);
	}
	return _register.intValue;
}

void handlePush(int opcode) {
	currentInstruction.opcode = opcode;
	currentInstruction.rs = readRegister();
	currentInstruction.rt = 0;
	currentInstruction.imm = 0;
}

void handleTwoRegisterArgIInstruction(int opcode) {
	currentInstruction.opcode = opcode;
	currentInstruction.rs = readRegister();
	eatComma();
	currentInstruction.rt = readRegister();
	currentInstruction.imm = 0;
}

void handleNormalIInstruction(int opcode) {
	currentInstruction.opcode = opcode;
	currentInstruction.rt = readRegister();
	eatComma();
	currentInstruction.rs = readRegister();
	eatComma();
	currentInstruction.imm = readImmediate();
}

void handleLui(int opcode) {
	currentInstruction.opcode = opcode;
	currentInstruction.rt = readRegister();
	eatComma();
	currentInstruction.imm = readImmediate();
}

void handlePop(int opcode) {
	currentInstruction.opcode = opcode;
	currentInstruction.rt = readRegister();
	currentInstruction.rs = 0;
	currentInstruction.imm = 0;
}

void (*handleITypeInstruction[])(int opcode) = {
	handleArglessIInstruction,			// nop
		handleArglessIInstruction,// stop
		handleTwoRegisterArgIInstruction,// cmp
		handleOneImmediateArgIInstruction,// jeq
		handleOneImmediateArgIInstruction,// jne
		handleOneImmediateArgIInstruction,// jlt
		handleOneImmediateArgIInstruction,// jgt
		handleOneImmediateArgIInstruction,// jle
		handleOneImmediateArgIInstruction,// jge
		handleOneImmediateArgIInstruction,// jmp
		handleOneImmediateArgIInstruction,// call
		handleArglessIInstruction,// ret
		handleNormalIInstruction,// load
		handleNormalIInstruction,// store
		handleNormalIInstruction,// addi
		handleArglessIInstruction,// syscall
		handlePush,// push
		handlePop,// pop
		handleLui,// lui
		handleNormalIInstruction,// ori
		handleNormalIInstruction,// sll
		handleNormalIInstruction,// srl
		handleNormalIInstruction,// sra
		handleOneImmediateArgIInstruction,// pushi
		handleNormalIInstruction// addiu
};

void initEverything() {
	init(&labelList);
	memset(sourceFile, 0, 65536);
	memset(dataSegment, 0, 4096);
	memset(textSegment, 0, 4096);
	populateRegisterList();
	populateITypeList();
	populateRTypeList();
	currentChar = 0;
	currentAddress = 0;
}

void mapToMemory(char* inputFileName) {
	int i = 0;
	FILE* fp;

	fp = fopen(inputFileName, "r");

	while (!feof(fp)) {
		sourceFile[i] = getc(fp);
		i++;
	}

	fclose(fp);
}

int isLabel(char* word) {
	return word[strlen(word) - 1] == ':' && word[0] != '\"';
}

int isInstruction(char* word) {
	stringInt dummy;
	return searchByString(ITypeList, word, &dummy)
			|| searchByString(RTypeList, word, &dummy);
}

void dataLabelScan() {
	char currentWord[256];
	stringInt label;

	readWord(currentWord);

	while (!endOfSegment()) {
		if (isLabel(currentWord)) {
			label.intValue = BASE_DATA + currentAddress;
			currentWord[strlen(currentWord) - 1] = '\0';	// Tirando o :
			strcpy(label.string, currentWord);
			addStringInt(&labelList, label);

			eatWhiteSpaces();
			if (sourceFile[currentChar] == '\"') {
				currentChar++;
				while (sourceFile[currentChar] != '\"') {
					if (sourceFile[currentChar] == '\\') {
						currentChar++;
					}
					currentAddress++;
					currentChar++;
				}
				currentAddress++;
				currentChar++;
			} else {
				fprintf(stderr, "Caracter inesperado em dataLabelScan: '%c'\n", sourceFile[currentChar]);
				exit(1);
			}
		} else {
			fprintf(stderr, "Palavra inesperada em dataLabelScan: '%s'\n", currentWord);
			exit(1);
		}
		readWord(currentWord);
	}
}

void textLabelScan() {
	char currentWord[256];
	stringInt label;

	readWord(currentWord);

	while (!endOfSegment()) {
		if (isLabel(currentWord)) {
			label.intValue = BASE_TEXT + currentAddress;
			currentWord[strlen(currentWord) - 1] = '\0';	// Tirando o :
			strcpy(label.string, currentWord);
			addStringInt(&labelList, label);
		} else if (isInstruction(currentWord)) {
			currentAddress += 4;
		} else if (strcasecmp(currentWord, ".data") == 0) {
			break;
		}
		readWord(currentWord);
	}
}

void labelScan() {
	char segment[256];
	readWord(segment);

	if (strcasecmp(segment, ".data") == 0) {
		dataLabelScan();
		currentAddress = 0;
		textLabelScan();
	} else if (strcasecmp(segment, ".text") == 0) {
		textLabelScan();
		currentAddress = 0;
		dataLabelScan();
	} else {
		fprintf(stderr, "Esperava '.data' ou '.text', mas leu '%s'.\n",
				segment);
		exit(1);
	}

	printContents(labelList);
}

void translateData() {
	char currentWord[256];

	readWord(currentWord);

	while (!endOfSegment() && strcasecmp(currentWord, ".text") != 0)   {
		if (isLabel(currentWord)) {
			eatWhiteSpaces();
			if (sourceFile[currentChar] == '\"') {
				currentChar++;
				while (sourceFile[currentChar] != '\"') {
					if (sourceFile[currentChar] == '\\') {
						dataSegment[currentAddress] = '\n';
						currentChar++;
					} else {
						dataSegment[currentAddress] = sourceFile[currentChar];
					}
					currentAddress++;
					currentChar++;
				}
				dataSegment[currentAddress] = '\0';
				currentAddress++;
				currentChar++;
			} else {
				fprintf(stderr, "Caracter inesperado em translateData: '%c'\n", sourceFile[currentChar]);
				exit(1);
			}
		}
		readWord(currentWord);
	}
	dataSegmentSize = currentAddress;
}

void handleRTypeInstruction(int instructionFunct) {
	currentInstruction.opcode = 0;
	currentInstruction.rd = readRegister();
	eatComma();
	currentInstruction.rs = readRegister();
	eatComma();
	currentInstruction.rt = readRegister();
	currentInstruction.funct = instructionFunct;
}

void write4BytesOnTextSegment(unsigned int toWrite) {
	textSegment[currentAddress] = (toWrite & 0xFF000000) >> 24;
	currentAddress++;
	textSegment[currentAddress] = (toWrite & 0x00FF0000) >> 16;
	currentAddress++;
	textSegment[currentAddress] = (toWrite & 0x0000FF00) >> 8;
	currentAddress++;
	textSegment[currentAddress] = (toWrite & 0x000000FF);
	currentAddress++;
}

void writeRInstruction() {
	unsigned int instructionBytecode = 0;

	instructionBytecode = (currentInstruction.rs << 21);
	instructionBytecode |= (currentInstruction.rt << 16);
	instructionBytecode |= (currentInstruction.rd << 11);
	instructionBytecode |= currentInstruction.funct;
	write4BytesOnTextSegment(instructionBytecode);
}

void writeIInstruction() {
	unsigned int instructionBytecode = 0;

	instructionBytecode = (currentInstruction.opcode << 26);
	instructionBytecode |= (currentInstruction.rs << 21);
	instructionBytecode |= (currentInstruction.rt << 16);
	instructionBytecode |= (unsigned short) currentInstruction.imm;
	write4BytesOnTextSegment(instructionBytecode);
}

void handleInstruction(char* instructionName) {
	stringInt instruction;

	if (searchByString(ITypeList, instructionName, &instruction)) {
		handleITypeInstruction[instruction.intValue](instruction.intValue);
		writeIInstruction();
	} else if (searchByString(RTypeList, instructionName, &instruction)) {
		handleRTypeInstruction(instruction.intValue);
		writeRInstruction();
	} else {
		fprintf(stderr, "Nope, ta errado, nao conheco essa instrucao: %s\n",
				instructionName);
		exit(1);
	}
}

void translateText() {
	char currentWord[256];

	readWord(currentWord);

	while (!endOfSegment()) {
		if (!isLabel(currentWord)) {
			if (isInstruction(currentWord)) {
				handleInstruction(currentWord);
			} else if (strcasecmp(currentWord, ".data") == 0) {
				break;
			} else {
				fprintf(stderr, "Huh?! O que e isso? %s\n", currentWord);
				exit(1);
			}
		}
		readWord(currentWord);
	}
	textSegmentSize = currentAddress;
}

void translate() {
	char segment[256];

	currentChar = 0;

	readWord(segment);

	if (strcasecmp(segment, ".data") == 0) {
		currentAddress = 0;
		translateData();
		currentAddress = 0;
		translateText();
	} else if (strcasecmp(segment, ".text") == 0) {
		currentAddress = 0;
		translateText();
		currentAddress = 0;
		translateData();
	} else {
		fprintf(stderr,
				"Esperava '.data' ou '.text' para traducao, mas leu '%s'.",
				segment);
		exit(1);
	}
}

short getExecutionStartAddress() {
	stringInt startLabel;

	if (!searchByString(labelList, "__start", &startLabel)) {
		fprintf(stderr, "Nao achei a label para __start.\n");
		exit(1);
	}
	return startLabel.intValue;
}

void writeToFile(char* output) {
	FILE* fp;
	unsigned int magicNumber = 0xDEADCAFE;
	char toWrite;
	short executionStartAddress;

	fp = fopen(output, "wb");

	// Little-endian inútil da porra...
	toWrite = (magicNumber & 0xFF000000) >> 24;
	fwrite(&toWrite, sizeof(char), 1, fp);
	toWrite = (magicNumber & 0x00FF0000) >> 16;
	fwrite(&toWrite, sizeof(char), 1, fp);
	toWrite = (magicNumber & 0x0000FF00) >> 8;
	fwrite(&toWrite, sizeof(char), 1, fp);
	toWrite = (magicNumber & 0x000000FF);
	fwrite(&toWrite, sizeof(char), 1, fp);

	executionStartAddress = getExecutionStartAddress();
	toWrite = (executionStartAddress & 0xFF00) >> 8;
	fwrite(&toWrite, sizeof(char), 1, fp);
	toWrite = (executionStartAddress & 0x00FF);
	fwrite(&toWrite, sizeof(char), 1, fp);

	toWrite = (dataSegmentSize & 0xFF00) >> 8;
	fwrite(&toWrite, sizeof(char), 1, fp);
	toWrite = (dataSegmentSize & 0x00FF);
	fwrite(&toWrite, sizeof(char), 1, fp);

	// Sério, vai tomar no cu, little endian

	fwrite(dataSegment, sizeof(char), dataSegmentSize, fp);
	fwrite(textSegment, sizeof(char), textSegmentSize, fp);

	fclose(fp);
}

void assemble(char* inputFileName, char* outputFileName) {
	initEverything();
	mapToMemory(inputFileName);
	labelScan();
	translate();
	writeToFile(outputFileName);
}

